﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using Kyoh.App.Crawler.Plugin;
using System.Runtime.InteropServices;
using Kyoh.Lib.Plugin;

namespace Crawler.Seeker.Ch2
{
    [Guid("591366A4-4498-4885-93F1-797C78574FD3")]
    public class Ch2Seeker : ISeeker
    {
        public IEnumerable<string> GetPages(IAnchor anchor)
        {
            bool first = false;
            if (anchor.Range < 1)
                first = true;
            else if (string.IsNullOrEmpty(anchor.Tag))
                first = true;
            else if (anchor.PieceId < 1)
                first = true;

            return first ? GetLinksFirst(anchor, 0) : GetLinksSecond(anchor);
        }

        static readonly Regex ReadCgiUri = new Regex(@"h?ttp://(?<host>\w+(\.2ch\.net|\.bbspink\.com))/test/read.cgi/(?<board>[^/]+)/(?<topic>\d+)/$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
        static readonly Regex UriRegex = new Regex(@"(h?ttps?://[^\s　<>]+)");
        private IEnumerable<string> GetLinksFirst(IAnchor anchor, long pieceId)
        {
            var req = CreateRequest(anchor);
            // (HTTPヘッダ)Accept-Encoding: gzip
            req.AutomaticDecompression = DecompressionMethods.GZip;

            var res = (HttpWebResponse)req.GetResponse();
            switch (res.StatusCode)
            {
                case HttpStatusCode.OK:
                    return ParseResponse(res, anchor, pieceId);
            }

            throw new Exception(string.Format("HttpStatusCode: {0} ({1})", res.StatusCode, res.StatusDescription));
        }

        private IEnumerable<string> GetLinksSecond(IAnchor anchor)
        {
            var req = CreateRequest(anchor);
            /*
                If-Modified-Since: Wed, 24 Dec 2008 21:14:12 GMT
                If-None-Match: "1a2dd38-446e-45ed15f9ad100"
                Range: bytes= 17517-
            //*/
            req.IfModifiedSince = anchor.DateTime;
            req.Headers[HttpRequestHeader.IfNoneMatch] = anchor.Tag;
            req.AddRange(anchor.Range-1);

            try
            {
                var res = (HttpWebResponse)req.GetResponse();
                switch (res.StatusCode)
                {
                    case HttpStatusCode.PartialContent:
                        return ParseResponse(res, anchor, 0);
                }

                throw new Exception(string.Format("HttpStatusCode: {0} ({1})", res.StatusCode, res.StatusDescription));
            }
            catch (WebException exc)
            {
                var res = exc.Response as HttpWebResponse;
                if (res != null)
                {
                    switch (res.StatusCode)
                    {
                        case HttpStatusCode.NotModified:
                            return Enumerable.Empty<string>();
                        case HttpStatusCode.RequestedRangeNotSatisfiable:
                            return GetLinksFirst(anchor, anchor.PieceId);
                    }
                }
                throw;
            }
        }

        private static IEnumerable<string> ParseResponse(HttpWebResponse response, IAnchor anchor, long pieceId)
        {
            anchor.Tag = response.GetResponseHeader("ETag");
            anchor.Range = long.Parse(response.GetResponseHeader("Content-Length"));
            anchor.DateTime = response.LastModified;
            using (var reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding("Shift_JIS")))
            {
                string piece;
                for (long r = 0; null != (piece = reader.ReadLine()); r++)
                {
                    if (r < pieceId)
                        continue;
                    var matches = UriRegex.Matches(piece);
                    foreach (Match m in matches)
                        yield return (m.Value.StartsWith("h") ? string.Empty : "h") + m.Value;
                    anchor.PieceId = r;
                }
            }
        }

        /*  GET /[板名]/dat/[スレッド番号].dat HTTP/1.1
            Host: [サーバー]
            Accept: * /*
            Referer: http://[サーバー]/test/read.cgi/[板名]/[スレッド番号]/
            Accept-Language: ja
            User-Agent: Monazilla/1.00 (ブラウザ名/バージョン)
            Connection: close
        //*/
        private static HttpWebRequest CreateRequest(IAnchor anchor)
        {
            var match = ReadCgiUri.Match(anchor.Uri);
            if (!match.Success)
                throw new NotSupportedException();

            var request = (HttpWebRequest)WebRequest.Create("http://" + match.Groups["host"].Value + "/" + match.Groups["board"].Value + "/dat/" + match.Groups["topic"].Value + ".dat");
            request.Accept = "*/*";
            request.Referer = anchor.Uri;
            request.UserAgent = "Monazilla/1.00";
            request.Headers[HttpRequestHeader.AcceptLanguage] = "ja";
            return request;
        }

        public bool Supports(IAnchor anchor)
        {
            return ReadCgiUri.IsMatch(anchor.Uri);
        }

        public string Author { get { return "kyoh"; } }
        public string Description { get { return string.Empty; } }
        public Uri Uri { get { return new Uri("http://kyoh.net/"); } }

        public object Preferences
        {
            get { return null; }
            set { }
        }
        public string Name { get { return "Ch2"; } }

        public void Dispose() { Dispose(true); }
        ~Ch2Seeker() { Dispose(false); }
        protected void Dispose(bool disposing) { }
    }
}
